<?php

namespace App\Services\Utils;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use BaiduBce\Services\Bos\BosClient;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Str;
use ZeonWang\BaiduBosClient\BosInterface;
use ZeonWang\BaiduBosClient\Facades\Bos;

trait Bosable
{
    protected $default_key_prefix = 'misc/';

    protected $start_try_count = 0;

    protected $max_try_count = 3;

    /**
     * @param UploadedFile $file
     * @param string $bos_key_prefix
     * @param bool $hash_key - 不使用
     * @param string $custom_ext - 自定义后缀
     * @return string
     * @throws \Exception
     */
    public function uploadPostFile(UploadedFile $file, $bos_key_prefix = '', $hash_key = true, $custom_ext = '')
    {
        $file_origin_name = $file->getClientOriginalName();
        $dir_name = $this->normalizeBosDirName($bos_key_prefix);

        if ($hash_key) {
            $hash = md5($file_origin_name . time());
            $time_stamp = date('_Y_m_d');
            $ext = $custom_ext ?: $file->guessClientExtension(); // 不要使用 $file->guessExtension() , 返回的是 "tmp"
            $dot_ext = $ext ? ".$ext" : '';

            $bos_key = $dir_name . $hash . $time_stamp . $dot_ext;
        } else {
            $bos_key = $dir_name . $file_origin_name;
        }

        /** @var BosClient $client */
        $client = Bos::client();

        $this->retryPutObjectFromFile($client, $bos_key, $file->getRealPath());
        
        return $bos_key;
    }

    /**
     * @param BosClient $client
     * @param $bos_key
     * @param string $file_path
     * @return mixed
     * @throws \Exception
     */
    private function retryPutObjectFromFile(BosClient $client,
                                            string $bos_key,
                                            string $file_path)
    {
        return retry($this->max_try_count, function () use ($client, $bos_key, $file_path) {
            // 尝试3次，每次间隔100毫秒
            $client->putObjectFromFile(Bos::bucket(), $bos_key, $file_path);
        }, 100);
    }

    /**
     *  保存指定名称
     *
     * @param UploadedFile $file
     * @param string $bos_key_prefix
     * @param $file_name
     * @return string
     * @throws \Exception
     */
    public function uploadPostFileWithName(UploadedFile $file, string $bos_key_prefix, string $file_name)
    {
        $dir_name = $this->normalizeBosDirName($bos_key_prefix);

        $bos_key = $dir_name . $file_name;

        /** @var BosClient $client */
        $client = Bos::client();

//        $client->putObjectFromFile(Bos::bucket(), $bos_key, $file->getRealPath());
        $this->retryPutObjectFromFile($client, $bos_key, $file->getRealPath());

        return $bos_key;
    }


    /**
     * @param $file_path
     * @param string $bos_key_prefix
     * @param bool $hash_key
     * @return string
     * @throws \Exception
     */
    public function uploadFile($file_path, $bos_key_prefix = '', $hash_key = true)
    {
        $prefix = $this->normalizeBosDirName($bos_key_prefix);

        if ($hash_key) {
            $time_stamp = date('_Y_m_d');
            $ext = pathinfo($file_path, PATHINFO_EXTENSION);
            $hash = md5($file_path . time());
            $bos_key = $prefix . $hash . $time_stamp . '.' . $ext;
        } else {
            $file_name = pathinfo($file_path, PATHINFO_BASENAME);
            $bos_key = $prefix . $file_name;
        }

//        $bos = app(BosInterface::class);
//
//        $bos->client->putObjectFromFile($bos->bucket, $bos_key, $file_path);
        /** @var BosClient $client */
        $client = Bos::client();

        $this->retryPutObjectFromFile($client, $bos_key, $file_path);

        return $bos_key;
    }

    /**
     * @param $key
     * @return string
     */
    public function getStaticUrl($key)
    {
        return head(explode('?', $this->getBosUrl($key)));
    }

    /**
     * @param string $bos_key
     * @return string
     */
    public function getBosUrl($bos_key)
    {
        if (mb_strpos($bos_key, 'http') === 0) return $bos_key;

        $file_url = Bos::client()
            ->generatePreSignedUrl(Bos::bucket(), $bos_key, Bos::sign_array());

        return $file_url;
    }

    /**
     * @param array $column_names
     * @throws \Exception
     */
    public function deleteBosColumns(array $column_names)
    {
        if (! is_array($column_names)) {
            throw new \Exception('column_names need to be an array');
        }

        array_map([$this, 'deleteBosColumn'], $column_names);
    }

    /**
     * @param array $column_names
     * @throws \Exception
     */
    public function deleteBosJsonColumns(array $column_names)
    {
        if (! is_array($column_names)) {
            throw new \Exception('column_names need to be an array');
        }

        array_map([$this, 'deleteBosJsonArr'], $column_names);
    }

    /**
     * @param string $column_name
     * @return bool|null|string
     * @throws \Exception
     */
    public function deleteBosColumn($column_name)
    {
        if (! $column_name || ! is_string($column_name)) {
            throw new \Exception('column_name need to be an column name');
        }

        if (! isset($this->attributes) || ! $this->attributes[$column_name]) return null;

        return $this->deleteBosKey($this->attributes[$column_name]);
    }

    /**
     * @param $key
     * @return bool
     */
    public function deleteBosKey($key): bool
    {
        if (! $this->isBosKeyValidated($key)) return false;

        /** @var BosClient $client */
        $client = Bos::client();
        try {
            $client->deleteObject(Bos::bucket(), $key);

            if (config('app.debug')) logger($key);

            return true;
        } catch (\Exception $e) {
            logger("[BOS ERROR] bos key: {$key} delete error: {$e->getMessage()}");

            report($e);

            return false;
        }
    }

    /**
     * @param $column_name
     * @return bool|null
     * @throws \Exception
     */
    public function deleteBosJsonArr($column_name)
    {
        if (! $column_name || ! is_string($column_name)) {
            throw new \Exception('column_name need to be an column name');
        }

        if (! isset($this->attributes) || ! $this->attributes[$column_name]) return null;

        $bos_key_arr = json_decode($this->attributes[$column_name]);

        if (! is_array($bos_key_arr) || ! count($bos_key_arr)) {
            return false;
        } else {
            array_map([$this, 'deleteBosKey'], $bos_key_arr);

            return true;
        }
    }

    /**
     * @param mixed $key
     * @return bool
     */
    private function isBosKeyValidated($key): bool
    {
        return $key && is_string($key) && mb_strpos($key, 'http') !== 0;
    }

    /**
     * @param $dir_name
     * @return string
     */
    private function normalizeBosDirName(string $dir_name = null): string
    {
        return $dir_name
            ? $this->padStringEnding($dir_name, '/')
            : $this->default_key_prefix;
    }

    /**
     * 如果字符串结尾没有以 ending 结尾，刚补上 ending
     *
     * @param string $str
     * @param string $ending
     * @return string
     */
    private function padStringEnding(string $str, string $ending): string
    {
        return Str::endsWith($str, $ending)
            ? $str
            : $str . $ending;
    }

}
